/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/effector/ChannelSplitter.h"
#include "alkes/media/LockImageBuffer.h"

namespace alkes {


ChannelSplitter* ChannelSplitter::create(ImageBufferFactory* factory)
{
    if (!factory)
        return 0;

    return new ChannelSplitter(factory);
}

void ChannelSplitter::destroy()
{
    delete this;
}

ChannelSplitter::ChannelSplitter(ImageBufferFactory* factory)
: ImageEffector(factory)
{
}

ChannelSplitter::~ChannelSplitter()
{
}

bool ChannelSplitter::split(ImageBuffer* src, intrusive_ptr<ImageBuffer>* dst[4])
{
    PixelFormat format = src->getPixelFormat();
    const PixelFormatTraits* traits = alkes_get_pixel_format_traits(format);

    bool ret = false;
    switch (traits->component_num)
    {
    case 1:
        ret = split1(src, dst);
        break;
    case 3:
        ret = split3(src, dst);
        break;
    case 4:
        ret = split3(src, dst);
        break;
    default:
        break;
    }

    return ret;
}

bool ChannelSplitter::split1(ImageBuffer* src, intrusive_ptr<ImageBuffer>* dst[4])
{
    if (AL_PIXEL_FORMAT_A8 != src->getPixelFormat())
        return false;

    if (!dst[3])
        return true;

    *dst[3] = cloneImageBuffer(src);

    return 0 != (*dst[3]);
}

bool ChannelSplitter::split3(ImageBuffer* src, intrusive_ptr<ImageBuffer>* dst[4])
{
    PixelFormat format = src->getPixelFormat();

    int bytes = alkes_get_byteperpixel(format);

    const ComponentInfo* ci[4];
    if (3 != getComponentInfo(format, ci))
        return false;

    return foreachSplit(src, ci, 3, dst, bytes);
}

bool ChannelSplitter::split4(ImageBuffer* src, intrusive_ptr<ImageBuffer>* dst[4])
{
    PixelFormat format = src->getPixelFormat();

    int bytes = alkes_get_byteperpixel(format);

    const ComponentInfo* ci[4];
    if (4 != getComponentInfo(format, ci))
        return false;

    return foreachSplit(src, ci, 4, dst, bytes);
}

struct uint24_t { uint8_t b[3]; };
template<typename T> struct MaskType { typedef T type; };
template<> struct MaskType<uint24_t> { typedef uint32_t type; };

template<typename T>
inline uint32_t getPixel(const T* p)
{
    return *p;
}

template<>
inline uint32_t getPixel<uint24_t>(const uint24_t* p)
{
    // TODO: Big endian???

    return ((uint32_t)p->b[0]) | (((uint32_t)p->b[1]) << 8) | (((uint32_t)p->b[2]) << 16);
}

template<typename T>
void splitChannel(
    uint16_t w, uint16_t h,
    const uint8_t* s_pixel_base, int32_t s_pitch,
    uint8_t* d_pixel_base, int32_t d_pitch,
    const uint8_t* ct, const ComponentInfo* ci)
{
    uint32_t mask = (uint32_t)(1 << ci->bits) - 1;
    int32_t shift = ci->offset;
    uint16_t x, y;
    const T* s_pixel;
    uint8_t* d_pixel;
    if (ct)
    {
        for (y = 0; y < h; ++y)
        {
            s_pixel = (const T*)(s_pixel_base + s_pitch * y);
            d_pixel = d_pixel_base + d_pitch * y;

            for (x = 0; x < w; ++x, ++s_pixel, ++d_pixel)
            {
                *d_pixel = ct[(getPixel<T>(s_pixel) >> shift) & mask];
            }
        }
    }
    else if (ci->bits == 8)
    {
        for (y = 0; y < h; ++y)
        {
            s_pixel = (const T*)(s_pixel_base + s_pitch * y);
            d_pixel = d_pixel_base + d_pitch * y;

            for (x = 0; x < w; ++x, ++s_pixel, ++d_pixel)
            {
                *d_pixel = (uint8_t)(getPixel<T>(s_pixel) >> shift);
            }
        }
    }
    else
    {
        for (y = 0; y < h; ++y)
        {
            s_pixel = (const T*)(s_pixel_base + s_pitch * y);
            d_pixel = d_pixel_base + d_pitch * y;

            for (x = 0; x < w; ++x, ++s_pixel, ++d_pixel)
            {
                *d_pixel = (uint8_t)(0xff * ((getPixel<T>(s_pixel) >> shift) & mask) / mask);
            }
        }
    }
}

bool ChannelSplitter::foreachSplit(ImageBuffer* src, const ComponentInfo** ci, int count, intrusive_ptr<ImageBuffer>* dst[4], int bytes)
{
    uint16_t w, h;
    src->getImageSize(w, h);

    intrusive_ptr<ImageBuffer> temp0(0), temp1(0), temp2(0), temp3(0);
    intrusive_ptr<ImageBuffer>* temp[4] = {
        &temp0, &temp1, &temp2, &temp3,
    };

    int i;
    for (i = 0; i < 4; ++i)
    {
        if (!dst[i])
            continue;

        *temp[i] = createImageBuffer(w, h, AL_PIXEL_FORMAT_A8);
        if (!(*temp[i]))
            return false;
    }


    LockImageBufferRead read_lock(src);
    if (0 == read_lock)
        return false;

    int32_t s_pitch = read_lock.getPitch();
    const uint8_t* s_pixel_base = read_lock;

    const uint8_t* ct;

    int32_t d_pitch;
    uint8_t* d_pixel_base;

    // For each component.
    for (i = 0; i < count; ++i, ++ci)
    {
        if (!dst[i])
            continue;

        LockImageBufferWrite write_lock(*temp[i]);
        if (0 == write_lock)
            break;

        d_pitch = write_lock.getPitch();
        d_pixel_base = (uint8_t*)write_lock;

        if ((*ci)->bits <= AL_LENGTH(CONVERT_TABLE))
            ct = CONVERT_TABLE[(*ci)->bits - 1];
        else
            ct = 0;

        switch (bytes)
        {
        case 1:
            splitChannel<uint8_t>(w, h, s_pixel_base, s_pitch, d_pixel_base, d_pitch, ct, *ci);
            break;
        case 2:
            splitChannel<uint16_t>(w, h, s_pixel_base, s_pitch, d_pixel_base, d_pitch, ct, *ci);
            break;
        case 3:
            splitChannel<uint24_t>(w, h, s_pixel_base, s_pitch, d_pixel_base, d_pitch, ct, *ci);
            break;
        case 4:
            splitChannel<uint32_t>(w, h, s_pixel_base, s_pitch, d_pixel_base, d_pitch, ct, *ci);
            break;
        }

        write_lock.detach();
    }

    read_lock.detach();

    bool ret = (i == count);

    if (ret)
    {
        // copy result.
        for (i = 0; i < count; ++i)
        {
            if (dst[i])
            {
                *dst[i] = static_cast<ImageBuffer*>(*temp[i]);
            }
        }
    }


    return ret;
}

}
